Skip to content

feat(agent-memory): Phase 8 — self-optimization config reads#256

Open
jamby77 wants to merge 4 commits into
feature/agent-memory-phase7-discoveryfrom
feature/agent-memory-phase8-config
Open

feat(agent-memory): Phase 8 — self-optimization config reads#256
jamby77 wants to merge 4 commits into
feature/agent-memory-phase7-discoveryfrom
feature/agent-memory-phase8-config

Conversation

@jamby77

@jamby77 jamby77 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #255 (Phase 7 — discovery marker).

What

Phase 8 of @betterdb/agent-memory: runtime-tunable recall/eviction knobs, so BetterDB Monitor's cache-proposals engine can retune a memory store without a restart (same pattern as semantic-cache's configRefresh).

MemoryStore reads {name}:__mem_config and live-applies:

  • recall.threshold (cosine-distance ceiling, 0..2)

  • recall.weights.similarity / recall.weights.recency / recall.weights.importance

  • recall.halfLifeSeconds

  • maxItemsPerScope

  • refreshConfig() (public, manual tick) reads the hash and applies it; currentConfig() exposes the effective tunables.

  • Opt-in configRefresh?: boolean | { enabled?, intervalMs? } enables an immediate read plus an unref'd interval (default 30s, min 1s); close() stops it.

Design / review notes

  • Off by default. Unlike semantic-cache (default-on), refresh is opt-in here so a standalone store never polls and existing unit tests keep their exact .call sequences.
  • Snapshot fallback. Each refresh rebuilds the config from the constructor values plus only the fields present in the hash, so removing a field reverts to the constructor default.
  • Validation. Invalid/out-of-range/NaN values are ignored; reads are best-effort and never throw. An all-zero weight vector is rejected (it would make every composite score 0 and leave recall ordering undefined) — kept from a review finding. initialWeights is copied so the shared DEFAULT_WEIGHTS constant can't be aliased.
  • recall() captures threshold+weights into locals before its first await, so an interval refresh can't tear an in-flight recall.

Tests (MemoryStore.config.test.ts, 12)

defaults snapshot · threshold · weights · halfLifeSeconds+maxItemsPerScope · partial config leaves others default · field-removal reverts · invalid ignored · all-zero weights rejected · live threshold affects recall end-to-end · no polling when disabled · immediate+interval read & stop on close (fake timers) · best-effort on read failure.

77/77 package tests green · tsc clean · prettier clean.


Note

Medium Risk
Changes live recall scoring, threshold filtering, and eviction victim selection when refresh is enabled; defaults stay off and invalid config is ignored, but mis-tuned remote hashes could alter memory behavior at runtime.

Overview
Adds opt-in runtime tuning for MemoryStore by reading {name}:__mem_config from Valkey, mirroring semantic-cache’s configRefresh pattern so Monitor can retune without a restart.

refreshConfig() and currentConfig() expose manual reads and the effective snapshot. configRefresh (off by default) triggers an immediate HGETALL plus an unref’d interval (default 30s, min 1s); close() clears the timer. Tunables include recall.threshold, partial recall weight fields, recall.halfLifeSeconds, and maxItemsPerScope, with validation, best-effort reads, rejection of all-zero weights, and constructor fallbacks when fields are missing.

recall() and enforceCapacity() snapshot half-life (and eviction weights) so a concurrent refresh cannot mix config versions mid-operation. New MemoryStore.config.test.ts and an eviction test cover the behavior; index.ts exports the new config types.

Reviewed by Cursor Bugbot for commit 0efd8ba. Bugbot is set up for automated code reviews on this repo. Configure here.

@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from eea7699 to ee647cc Compare June 17, 2026 14:50
Comment thread packages/agent-memory/src/MemoryStore.ts
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 524f3a8 to e58dbb7 Compare June 18, 2026 06:57
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from 14b6e7c to 9412e5a Compare June 18, 2026 06:57
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from e58dbb7 to 0d89d67 Compare June 18, 2026 07:19
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from 9412e5a to 37c7a4b Compare June 18, 2026 07:19

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 37c7a4b. Configure here.

Comment thread packages/agent-memory/src/MemoryStore.ts
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 0d89d67 to 73d91d7 Compare June 18, 2026 07:28
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from 37c7a4b to 06b56f6 Compare June 18, 2026 07:28

@KIvanow KIvanow left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phase 8 is in good shape: off-by-default keeps existing stores quiet, the before-await snapshots are genuinely tear-free since applyConfig is synchronous and reassigns rather than mutates, the all-zero weights rejection is the right guard, and the config key stays clear of the index prefix. One thing I would like addressed:

A partial weights write silently resets the omitted components to constructor defaults. Because applyConfig rebuilds the weights vector from {...initialWeights} on every pass, a config hash that sets only recall.weights.similarity snaps recency and importance back to the constructor values (0.25 / 0.15) rather than preserving the currently-live ones. That is consistent with the documented snapshot-fallback semantics, but for Monitor's proposal engine, which is likely to nudge a single knob, it is a footgun: tuning one weight silently retunes the other two. Please either preserve the live weight components when only a subset is present, or document clearly that weights must always be written as a full triple so a partial proposal does not quietly reset the rest.

@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 73d91d7 to 8bc0c13 Compare June 19, 2026 09:51
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from 7d0fa87 to 90ac526 Compare June 19, 2026 09:51
@jamby77

jamby77 commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator Author

@KIvanow Fixed in 90ac526: a partial weights write now starts from the live weights and overlays only the components present, so nudging one knob no longer resets the other two. A fully-absent weights config still falls back to the constructor values. Added a test for partial preservation.

@jamby77 jamby77 requested a review from KIvanow June 19, 2026 09:52
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 8bc0c13 to 9f8485a Compare June 19, 2026 10:01
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch 2 times, most recently from f325dee to 94e4101 Compare June 19, 2026 11:47
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 94d2a7d to 66c0809 Compare June 19, 2026 13:10
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from 94e4101 to ff2e1f1 Compare June 19, 2026 13:10
jamby77 added 4 commits June 19, 2026 16:32
- MemoryStore reads {name}:__mem_config on an opt-in configRefresh
  interval and live-applies recall.threshold, recall.weights.*,
  recall.halfLifeSeconds, and maxItemsPerScope without a restart
- Absent fields fall back to constructor values; invalid values are
  ignored; reads are best-effort and never throw
- Reject an all-zero weight vector to keep recall ordering well-defined
- Add currentConfig() to expose the effective tunables; close() also
  stops the refresh interval
- Refresh is off by default (opt-in) so a standalone store never polls
Capture halfLifeSeconds with threshold/weights before the first await so a
concurrent configRefresh can't score a single recall with a mix of config
versions.
enforceCapacity read weights and halfLifeSeconds from instance fields at
selectEvictions time, several awaits after the capacity check. With opt-in
configRefresh a refresh could land mid-pass and score victims with a
different tunable set. Snapshot weights and halfLifeSeconds at entry,
matching the recall snapshot, so a pass uses one consistent config.
…g write

applyConfig rebuilt the weights vector from the constructor defaults each
pass, so a config that set only one weight component silently reset the other
two — a footgun for the proposal engine, which nudges a single knob. A partial
write now starts from the live weights and overlays only what's present; a
fully-absent weights config still falls back to the constructor values.
@jamby77 jamby77 force-pushed the feature/agent-memory-phase7-discovery branch from 66c0809 to c83f1e6 Compare June 19, 2026 13:34
@jamby77 jamby77 force-pushed the feature/agent-memory-phase8-config branch from ff2e1f1 to 0efd8ba Compare June 19, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants